/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#ifndef PLARRAY_H
#define PLARRAY_H

#include <type_traits>
#include <kernel/PLObject.h>
#include <PLExceptionBase.h>
#include <exception/PLElementNotFoundException.h>
#include <exception/PLIndexOutOfRangeException.h>

namespace PLSDK {

	/**
	 * Class to manage a array.
	 */
	template < typename T >
	class PLArray : public PLObject {
	public:
		/**
		 * Constructor.
         *
         * @param clear Clear the memory.
		 */
		PLArray(bool clear = false);
		/**
		 * Constructor.
		 *
		 * @param size The size of the array.
         * @param clear Clear the memory.
		 */
		PLArray(pluint size, bool clear = false);
		/**
		 * Copy constructor.
		 *
		 * @param data The data array.
		 * @param size The size of the array.
         * @param clear Clear the memory.
		 * @warning The data for the array was not deleted at the deleting of the PLArray.
		 */
		PLArray(T *data, pluint size, bool clear = false);
		/**
		 * Copy constructor.
		 *
		 * @param array The array to copy.
		 * @param copy True to copy the data to the new array.
		 */
		PLArray(PLArray<T> *array, plbool copy = false);
		/**
		 * Copy constructor.
		 *
		 * @param array The array to copy.
		 * @param copy True to copy the data to the new array.
		 */
		PLArray(const PLArray<T> &array, plbool copy = false);
		/**
		 * Copy constructor.
		 *
		 * @param array The array to copy.
		 * @param start The start position to copy.
		 * @param end The end position to copy.
         * @param clear Clear the memory.
		 */
		PLArray(const PLArray<T> &array, pluint start, pluint end, bool clear = false);
		/**
		 * Destructor.
		 */
		~PLArray();
		/**
		 * Get the size of the array.
		 *
		 * @return The size of the array.
		 */
		inline pluint size() const;
		/**
		 * Is the array empty. This was true if the array have a size from 0.
		 *
		 * @return True if the array was empty.
		 */
		inline plbool isEmpty() const;
		/**
		 * Clear the buffer and delete all memory that was mapped for the buffer.
		 */
		inline void clear();
		/**
		 * Delete the old array and create a array with the new size. Optional you
		 * can copy the old data to the new array if the size of the new array have the
		 * same size of the data of the old array or it is bigger. If it war smaller a excpetion
		 * was thrown.
		 *
		 * @param size The new size of the array.
		 * @param copy Set this to true to copy the old data of the array.
		 * @exception PLIndexOutOfRangeException This excpetion was thrown if the size of the new array
		 * was to small and you whant to copy the data in the new array.
		 * @exception PLElementNotFoundException If the old array have no data and you whant to copy the
		 * data of the old array into the new array.
		 */
		inline void resetSizeOfArray(pluint size, plbool copy = false);
		/**
		 * Get a element from the array.
		 *
		 * @param pos Position to get the element from.
		 * @return The data.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline T get(pluint pos) const;
		/**
		 * Get a element from the array.
		 *
		 * @param pos Position to get the element from.
		 * @return The data.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline T at(pluint pos) const;
		/**
		 * Set the value of the array.
		 *
		 * @param pos The position to change the array.
		 * @param value The value to set.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline void set(pluint pos, T value);
		/**
		 * Set the value of the array.
		 *
		 * @param pos The data array to set to the array.
		 * @param length The length of the data to set.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline void setArray(T *pos, pluint length);
		/**
		 * Get the part of the array from the beginning with the length of length.
		 *
		 * @param length The length of the data.
		 * @return The data from the array.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 * @warning The return array was not deleted from this class it was in you're hand to
		 * delete it.
		 */
		inline T *part(pluint length);
		/**
		 * Get the part of the array from the beginning with the length of length.
		 *
		 * @param length The length of the data.
		 * @return The data from the array.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline PLArray<T> arrayPart(pluint length);
		/**
		 * Get the part of the array from the position start with the length of length.
		 *
		 * @param start The start position of the data.
		 * @param end The end point of the data to get.
		 * @return The data from the array.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 * @warning The return array was not deleted from this class it was in you're hand to
		 * delete it.
		 */
		inline T *part(pluint start, pluint end);
		/**
		 * Get the part of the array from the position start with the length of length.
		 *
		 * @param start The start position of the data.
		 * @param end The end point of the data to get.
		 * @return The data from the array.
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline PLArray<T> arrayPart(pluint start, pluint end);
		/**
		 * Operator for one element.
		 *
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline T &operator[](pluint pos);
		/**
		 * Operator for one element.
		 *
		 * @exception PLIndexOutOfRangeException If the position was not in the range of the
		 * array range this exception was thrown.
		 * @exception PLElementNotFoundException If the array was a empty array.
		 */
		inline const T &operator[](pluint pos) const;
		/**
		 * Copy operator.
		 */
		inline PLArray<T> &operator =(const PLArray<T> &array);
		/**
		 * Get the pointer of the array.<br>
		 * This function you must not use if you only use the pl types. It exists to use system functions
		 * that use only the C/C++ types.
		 *
		 * @return The pointer of the data from the array.
		 * @warning The returned array was deleted if the instance of this array was deleted.
		 */
		inline const T *constData() const;
		/**
		 * Get the pointer of the array.<br>
		 * This function you must not use if you only use the pl types. It exists to use system functions
		 * that use only the C/C++ types.
		 *
		 * @return The pointer of the data from the array.
		 * @warning The returned array was deleted if the instance of this array was deleted.
		 */
		inline T *data() const;
	private:
		T *m_data;
		pluint m_size;
		pluint *m_reference;
		plbool m_delete;
        bool m_clear;
	};

	template < typename T >
	PLArray<T>::PLArray(bool _clear) : PLObject(), m_data(0), m_size(0), m_reference(new pluint(0)), m_delete(true), m_clear(_clear) {
	}

	template < typename T >
	PLArray<T>::PLArray(pluint _size, bool _clear) : PLObject(), m_data(0), m_size(_size), m_reference(new pluint(0)), m_delete(true), m_clear(_clear) {
		if(_size > 0) {
			m_data = new T [(int)_size];
            if(m_clear) {
                for(pluint i = 0 ; i < m_size ; i++) {
                    if(is_pointer<T>::value) {
                        m_data[i] = 0;
                    } else {
                        m_data[i] = T();
                    }
                }
            }
		}
	}

	template < typename T >
	PLArray<T>::PLArray(T *_data, pluint _size, bool _clear) : PLObject(), m_size(_size), m_reference(new pluint(0)), m_delete(false), m_clear(_clear) {
		m_data = _data;
	}

	template < typename T >
	PLArray<T>::PLArray(PLArray<T> *array, plbool copy) : PLObject(), m_size(array->m_size), m_delete(true), m_clear(array.m_clear) {
		if(!copy) {
			m_data = array->m_data;
			m_reference = array->m_reference;
			(*m_reference)++;
		} else {
			if(array->isEmpty()) {
				m_data = 0;
			} else {
				m_data = new T[array->m_size];
				for(pluint i = 0 ; i < array->m_size ; i++) {
					m_data[i] = (*array)[i];
				}
			}
			m_reference = new pluint(0);
		}
	}

	template < typename T >
	PLArray<T>::PLArray(const PLArray<T> &array, plbool copy) : PLObject(), m_size(array.m_size), m_delete(true), m_clear(array.m_clear) {
		if(!copy) {
			m_data = array.m_data;
			m_reference = array.m_reference;
			(*m_reference)++;
		} else {
			if(array.isEmpty()) {
				m_data = 0;
			} else {
				m_data = new T[array.m_size];
				for(pluint i = 0 ; i < array.m_size ; i++) {
					m_data[i] = array.get(i);
				}
			}
			m_reference = new pluint(0);
		}
	}

	template < typename T >
	PLArray<T>::PLArray(const PLArray<T> &array, pluint start, pluint end, bool _clear) : PLObject(), m_delete(true), m_clear(_clear) {
		if(array.isEmpty()) {
			m_data = 0;
			m_size = 0;
		} else {
			m_data = new T[end - start];
			for(pluint i = start ; i < end ; i++) {
				m_data[i] = array.get(i);
			}
			m_size = end - start;
		}
		m_reference = new pluint(0);
	}

	template < typename T >
	PLArray<T>::~PLArray() {
		if(m_reference != 0 && (*m_reference) == 0 && m_data != 0 && m_delete) {
			delete[] m_data;
			m_data = 0;
			delete m_reference;
			m_reference = 0;
		} else if(m_reference != 0 && m_delete) {
			(*m_reference)--;
		}
	}

	template < typename T >
	pluint PLArray<T>::size() const {
		return m_size;
	}

	template < typename T >
	plbool PLArray<T>::isEmpty() const {
		return m_data == 0;
	}

	template < typename T >
	void PLArray<T>::clear() {
		if(m_data != 0) {
			delete [] m_data;
			m_data = 0;
			delete m_reference;
			m_reference = 0;
		}
		m_data = 0;
		m_size = 0;
	}

	template < typename T >
	void PLArray<T>::resetSizeOfArray(pluint _size, plbool copy) {
		T *tmp = new T[_size];
		if(copy) {
			if(_size <= m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			if(m_data == 0) {
				plthrow(PLElementNotFoundException, plthrowEmpty());
			}
			for(pluint i = 0 ; i < m_size ; i++) {
				tmp[i] = m_data[i];
			}
            if(m_clear) {
                for(pluint i = m_size ; i < _size ; i++) {
                    if(is_pointer<T>::value) {
                        tmp[i] = 0;
                    } else {
                        tmp[i] = T();
                    }
                }
            }
		}
		if(m_data != 0) {
			delete [] m_data;
		}
		m_data = tmp;
		m_size = _size;
	}

	template < typename T >
	T PLArray<T>::get(pluint pos) const {
		if(m_data != 0) {
			if(pos >= m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			return m_data[pos];
		}
		plthrow(PLElementNotFoundException, plthrowEmpty());
        return T();
	}

	template < typename T >
	T PLArray<T>::at(pluint pos) const {
		return get(pos);
	}

	template < typename T >
	void PLArray<T>::set(pluint pos, T value) {
		if(m_data != 0) {
			if(pos >= m_size || pos < 0) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			m_data[pos] = value;
		} else {
			plthrow(PLElementNotFoundException, plthrowEmpty());
		}
	}

	template < typename T >
	void PLArray<T>::setArray(T *pos, pluint length) {
		if(m_data != 0) {
			if(length >= m_size || length <= 0) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			m_data[length] = pos[length];
		} else {
			plthrow(PLElementNotFoundException, plthrowEmpty());
		}
	}

	template < typename T >
	T &PLArray<T>::operator[](pluint pos) {
		if(m_data != 0) {
			if(pos >= m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			return m_data[pos];
		}
        plthrow(PLElementNotFoundException, plthrowEmpty());
        static T ret;
        return ret;
	}

	template < typename T >
	const T &PLArray<T>::operator[](pluint pos) const {
		if(m_data != 0) {
			if(pos >= m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			return m_data[pos];
		} else {
			plthrow(PLElementNotFoundException, plthrowEmpty());
		}
        static T ret;
        return ret;
	}

	template < typename T >
	const T *PLArray<T>::constData() const {
		return m_data;
	}

	template < typename T >
	T *PLArray<T>::data() const {
		return m_data;
	}

	template < typename T >
	T *PLArray<T>::part(pluint length) {
		if(m_data != 0) {
			if(length > m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			T *ret = new T[length];
			for(pluint i = 0 ; i < length ; i++) {
				ret[i] = m_data[i];
			}
			return ret;
		}
		plthrow(PLElementNotFoundException, plthrowEmpty());
	}

	template < typename T >
	PLArray<T> PLArray<T>::arrayPart(pluint length) {
		if(m_data != 0) {
			if(length > m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			PLArray<T> ret(length);
			for(pluint i = 0 ; i < length ; i++) {
				ret[i] = m_data[i];
			}
			return ret;
		}
		plthrow(PLElementNotFoundException, plthrowEmpty());
	}

	template < typename T >
	T *PLArray<T>::part(pluint start, pluint end) {
		if(m_data != 0) {
			if(start > m_size || end > m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			T *ret = new T[end - start + 1];
			for(pluint i = start ; i <= end ; i++) {
				ret[i - start] = m_data[i];
			}
			return ret;
		}
		plthrow(PLElementNotFoundException, plthrowEmpty());
	}

	template < typename T >
	PLArray<T> PLArray<T>::arrayPart(pluint start, pluint end) {
		if(m_data != 0) {
			if(start > m_size || end > m_size) {
				plthrow(PLIndexOutOfRangeException, plthrowEmpty());
			}
			PLArray<T> ret(end - start + 1);
			for(pluint i = start ; i <= end ; i++) {
				ret[i - start] = m_data[i];
			}
			return ret;
		}
		plthrow(PLElementNotFoundException, plthrowEmpty());
	}

	template < typename T >
	PLArray<T> &PLArray<T>::operator =(const PLArray<T> &array) {
		if(array.isEmpty()) {
			m_data = 0;
		} else {
			m_data = new T[array.m_size];
			for(pluint i = 0 ; i < array.m_size ; i++) {
				m_data[i] = array.get(i);
			}
		}
		m_reference = 0;
		return *this;
	}

}

#endif
